"""流程定义同步工具。

提供解析 BPMN XML 并写入 mx_process_task_definitions 表的通用方法，
供部署脚本、API 调用复用。
"""
from __future__ import annotations

from typing import Dict, List, Optional
import json
import xml.etree.ElementTree as ET

from ..models import ProcessTaskDefinition
from .. import db


NS = {
    'bpmn2': 'http://www.omg.org/spec/BPMN/20100524/MODEL',
    'flowable': 'http://flowable.org/bpmn'
}

def _extract_sequence_conditions(process_el: ET.Element) -> Dict[str, List[str]]:
    """提取单个流程内 sequenceFlow 到目标节点的条件表达式列表。"""
    mapping: Dict[str, List[str]] = {}
    for sf in process_el.findall('bpmn2:sequenceFlow', NS):
        target = sf.get('targetRef')
        cond_el = None
        for child in list(sf):
            if 'conditionExpression' in child.tag:
                cond_el = child
                break
        expr = cond_el.text.strip() if cond_el is not None and (cond_el.text or '').strip() else None
        if target and expr:
            mapping.setdefault(target, []).append(expr)
    return mapping


def parse_bpmn_process_tasks(xml_text: str) -> Dict[str, List[Dict[str, object]]]:
    """解析 BPMN XML，返回 {process_key: [任务定义]}。"""
    processes: Dict[str, List[Dict[str, object]]] = {}
    try:
        root = ET.fromstring(xml_text)
    except ET.ParseError as exc:
        raise ValueError(f"BPMN XML 解析失败: {exc}") from exc

    for process_el in root.findall('bpmn2:process', NS):
        process_key = process_el.get('id')
        if not process_key:
            continue
        conditions_map = _extract_sequence_conditions(process_el)
        task_defs: List[Dict[str, object]] = []

        def append_node(node: ET.Element,
                        activity_type: str,
                        conditions: List[str],
                        assignee: Optional[str] = None):
            aid = node.get('id')
            if not aid:
                return
            name = node.get('name') or aid
            task_defs.append({
                'activity_id': aid,
                'activity_name': name,
                'activity_type': activity_type,
                'conditions': conditions,
                'assignee': assignee
            })

        for se in process_el.findall('bpmn2:startEvent', NS):
            append_node(se, 'startEvent', [], None)
        for ut in process_el.findall('bpmn2:userTask', NS):
            # Flowable namespace attribute holds the assignee expression if configured.
            assignee = ut.get(f"{{{NS['flowable']}}}assignee")
            append_node(ut, 'userTask', conditions_map.get(ut.get('id'), []), assignee)
        for ee in process_el.findall('bpmn2:endEvent', NS):
            append_node(ee, 'endEvent', [], None)

        processes[process_key] = task_defs

    return processes


def sync_process_task_definitions(definitions: Dict[str, List[Dict[str, object]]],
                                  definition_id_map: Dict[str, str]) -> int:
    """根据 {process_key: tasks} 与 key->definitionId 映射写入数据库。

    返回写入的节点数量。
    """
    total = 0
    for process_key, tasks in definitions.items():
        proc_def_id = definition_id_map.get(process_key)
        if not proc_def_id:
            continue
        ProcessTaskDefinition.query.filter_by(process_definition_id=proc_def_id).delete()
        for idx, task in enumerate(tasks):
            db.session.add(ProcessTaskDefinition(
                process_definition_id=proc_def_id,
                activity_id=task['activity_id'],
                activity_name=task['activity_name'],
                activity_type=task['activity_type'],
                sequence_index=idx,
                conditions_json=json.dumps(task.get('conditions', []), ensure_ascii=False),
                assignee=task.get('assignee')
            ))
            total += 1
    db.session.commit()
    return total
